<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/6/14
 * Time: 17:00
 */

namespace other;
use think\Controller;
use think\Db;

class DbManage extends Controller
{
    // 换行符
    private $ds = "\n";
    // 存储SQL的变量
    public $sqlContent = "";
    // 每条sql语句的结尾符
    public $sqlEnd = ";";

    /*
     * 新增查询数据库表
     */
    function getTables()
    {
        $res    = Db::query("SHOW TABLES");
        $config = config('database');
        $tables = [];
        foreach ($res as $key => $value) {
            array_push($tables, $value['Tables_in_'.$config['database']]);
        }
        return $tables;
    }

    /*
     *
     * ------------------------------------------数据库备份start----------------------------------------------------------
     */

    /**
     * 数据库备份
     * 参数：备份哪个表(可选),备份目录(可选，默认为backup),分卷大小(可选,默认2000，即2M)
     *
     * @param $string $dir
     * @param int $size
     * @param $string $tablename
     */
    function backup($tablename = '', $dir = './runtime/data/', $size = 204800)
    {
        $dir = $dir ? $dir : './';
        // 创建目录
        if (!is_dir($dir)) {
            @mkdir($dir, 0777, true) or die ('创建文件夹失败');
        }
        $size = $size ? $size : 1024 * 20;
        $sql = '';
        // 只备份某个表
        if (!empty ($tablename)) {
            $row = Db::query("SHOW TABLES LIKE '" . $tablename . "'");
            if ($row != 1) {
            } else {
                return ['status'=>false, 'msg'=>'表-' . $tablename . '-不存在，请检查！'];
            }
            // 插入dump信息
            $sql = $this->_retrieve();
            // 插入表结构信息
            $sql .= $this->_insert_table_structure($tablename);
            // 插入数据
            $data = Db::query("select * from " . $tablename);
            //$data = mysql_query("select * from " . $tablename);
            // 文件名前面部分
            $filename = date('YmdHis') . "_" . $tablename;

            // 字段数量
            //$num_fields = Db::query("select count(*) from information_schema.COLUMNS where table_name='ceshi'");
            //$num_fields = $num_fields[0]['num'];
            // 第几分卷
            $p = 1;
            // 循环每条记录
            foreach ($data as $key => $value) {
                // 单条记录
                $sql .= $this->_insert_record($tablename, $value);
                // 如果大于分卷大小，则写入文件
                if (strlen($sql) >= $size * 1024) {
                    $file = $filename . "_v" . $p . ".sql";
                    if (!($this->_write_file($sql, $file, $dir)))
                    {
                        return ['status'=>false, 'msg'=>'备份表 -' . $tablename . '- 失败'];
                    }
                    // 下一个分卷
                    $p++;
                    // 重置$sql变量为空，重新计算该变量大小
                    $sql = "";
                }
            }
            // 及时清除数据
            unset($data);
            // sql大小不够分卷大小
            if ($sql != "") {
                $filename .= "_v" . $p . ".sql";
                if (!($this->_write_file($sql, $filename, $dir)))
                {
                    return ['status'=>false, 'msg'=>'备份卷 -' . $p . '- 失败'];
                }
            }
            return ['status'=>true, 'msg'=>'恭喜您! 备份成功'];
        } else {
            // 备份全部表
            $tables = Db::query("show table status from " . config('database')['database']);
            // 插入dump信息
            $sql .= $this->_retrieve();
            // 文件名前面部分
            $filename = date('YmdHis') . "_all";
            // 查出所有表
            $tables = Db::query('SHOW TABLES');
            // 第几分卷
            $p = 1;
            // 循环所有表
            foreach ($tables as $key => $value) {
                // 获取表名
                $tablename = $value['Tables_in_'.config('database')['database']];
                // 获取表结构
                $sql .= $this->_insert_table_structure($tablename);
                $data = Db::query("select * from " . $tablename);
                // 循环每条记录
                foreach ($data as $key => $val) {
                    // 单条记录
                    $sql .= $this->_insert_record($tablename, $val);
                    // 如果大于分卷大小，则写入文件
                    if (strlen($sql) >= $size * 1024) {
                        $file = $filename . "_v" . $p . ".sql";
                        if (!($this->_write_file($sql, $file, $dir)))
                        {
                            return ['status'=>false, 'msg'=>'备份表 -' . $tablename . '- 失败'];
                        }
                        // 下一个分卷
                        $p++;
                        // 重置$sql变量为空，重新计算该变量大小
                        $sql = "";
                    }
                }
            }
            // sql大小不够分卷大小
            if ($sql != "") {
                $filename .= "_v" . $p . ".sql";
                if (!($this->_write_file($sql, $filename, $dir)))
                {
                    return ['status'=>false, 'msg'=>'备份卷 -' . $p . '- 失败'];
                }
            }
            return ['status'=>true, 'msg'=>'恭喜您! 备份成功'];
        }
    }

    /**
     * 插入数据库备份基础信息
     *
     * @return string
     */
    private function _retrieve()
    {
        $mysql = Db::query("select VERSION() as version");
        $mysql = $mysql[0]['version'];
        $mysql = empty($mysql) ? '未知' : $mysql;
        $value = '';
        $value .= '--' . $this->ds;
        $value .= '-- MySQL database dump' . $this->ds;
        $value .= '-- Created by DbManage class, Power By QuBao. ' . $this->ds;
        $value .= '--' . $this->ds;
        $value .= '-- 生成日期: ' . date('Y') . ' 年  ' . date('m') . ' 月 ' . date('d') . ' 日 ' . date('H:i') . $this->ds;
        $value .= '-- MySQL版本: ' . $mysql . $this->ds;
        $value .= '-- PHP 版本: ' . phpversion() . $this->ds;
        $value .= $this->ds;
        $value .= '--' . $this->ds;
        $value .= '--' . $this->ds . $this->ds;
        $value .= '-- -------------------------------------------------------';
        $value .= $this->ds . $this->ds;
        return $value;
    }

    /**
     * 插入表结构
     *
     * @param unknown_type $table
     * @return string
     */
    private function _insert_table_structure($table)
    {
        $sql = '';
        $sql .= "--" . $this->ds;
        $sql .= "-- 表的结构" . $table . $this->ds;
        $sql .= "--" . $this->ds . $this->ds;

        // 如果存在则删除表
        $sql .= "DROP TABLE IF EXISTS `" . $table . '`' . $this->sqlEnd . $this->ds;
        // 获取详细表信息
        $res = Db::query('SHOW CREATE TABLE `' . $table . '`');
        $sql .= $res[0]['Create Table'];
        $sql .= $this->sqlEnd . $this->ds;
        // 加上
        $sql .= $this->ds;
        $sql .= "--" . $this->ds;
        $sql .= "-- 转存表中的数据 " . $table . $this->ds;
        $sql .= "--" . $this->ds;
        $sql .= $this->ds;
        return $sql;
    }

    /**
     * 插入单条记录
     *
     * @param string $table
     * @param int $num_fields
     * @param array $record
     * @return string
     */
    private function _insert_record($table, $record)
    {
        // sql字段逗号分割
        $insert = '';
        $comma = "";
        $insert .= "INSERT INTO `" . $table . "` VALUES(";
        // 循环每个子段下面的内容
        foreach ($record as $key => $value) {
            $insert .= ($comma . "'" . addslashes($record[$key]) . "'");
            $comma = ",";
        }
        $insert .= ");" . $this->ds;
        return $insert;
    }

    /**
     * 写入文件
     *
     * @param string $sql
     * @param string $filename
     * @param string $dir
     * @return boolean
     */
    private function _write_file($sql, $filename, $dir)
    {
        $dir = $dir ? $dir : './runtime/data/';
        // 创建目录
        if (!is_dir($dir)) {
            mkdir($dir, 0777, true);
        }
        $re = true;
        if (!@$fp = fopen($dir . $filename, "w+")) {
            $re = false;
        }
        if (!@fwrite($fp, $sql)) {
            $re = false;
        }
        if (!@fclose($fp)) {
            $re = false;
        }
        return $re;
    }

    /*
     *
     * -------------------------------上：数据库导出-----------分割线----------下：数据库导入--------------------------------
     */

    /**
     * 导入备份数据
     * 说明：分卷文件格式20120516211738_all_v1.sql
     * 参数：文件路径(必填)
     *
     * @param string $sqlfile
     */
    function restore($sqlfile)
    {
        // 检测文件是否存在
        if (!file_exists($sqlfile)) {
            return ['status'=>false, 'msg'=>'sql文件不存在！请检查'];
        }
        $this->lock($this->database);
        // 获取数据库存储位置
        $sqlpath = pathinfo($sqlfile);
        $this->sqldir = $sqlpath ['dirname'];
        // 检测是否包含分卷，将类似20120516211738_all_v1.sql从_v分开,有则说明有分卷
        $volume = explode("_v", $sqlfile);
        $volume_path = $volume [0];
        if (empty ($volume [1])) {
            // 没有分卷
            if ($this->_import($sqlfile)) {
                return ['status'=>true, 'msg'=>'数据库导入成功！'];
            } else {
                return ['status'=>false, 'msg'=>'数据库导入失败！'];
            }
        } else {
            // 存在分卷，则获取当前是第几分卷，循环执行余下分卷
            $volume_id = explode(".sq", $volume [1]);
            // 当前分卷为$volume_id
            $volume_id = intval($volume_id [0]);
            while ($volume_id) {
                $tmpfile = $volume_path . "_v" . $volume_id . ".sql";
                // 存在其他分卷，继续执行
                if (file_exists($tmpfile)) {
                    // 执行导入方法
                    if (!$this->_import($tmpfile)) {
                        return ['status'=>false, 'msg'=>'导入分卷：' . $tmpfile . '失败！可能是数据库结构已损坏！请尝试从分卷1开始导入'];
                    }
                }
                $volume_id++;
            }
        }
        if (empty ($volume [1])) {
            // 没有分卷
            if ($this->_import($sqlfile)) {
                return ['status'=>true, 'msg'=>'数据库导入成功！'];
            } else {
                return ['status'=>false, 'msg'=>'数据库导入失败！'];
            }
        } else {
            // 存在分卷，则获取当前是第几分卷，循环执行余下分卷
            $volume_id = explode(".sq", $volume [1]);
            // 当前分卷为$volume_id
            $volume_id = intval($volume_id [0]);
            while ($volume_id) {
                $tmpfile = $volume_path . "_v" . $volume_id . ".sql";
                // 存在其他分卷，继续执行
                if (file_exists($tmpfile)) {
                    // 执行导入方法
                    if (!$this->_import($tmpfile)) {
                        return ['status'=>false, 'msg'=>'导入分卷：' . $tmpfile . '失败！可能是数据库结构已损坏！请尝试从分卷1开始导入'];
                    }
                }
                $volume_id++;
            }
        }
    }

    /**
     * 将sql导入到数据库（普通导入）
     *
     * @param string $sqlfile
     * @return boolean
     */
    public function _import($sqlfile)
    {
        // sql文件包含的sql语句数组
        $sqls = array();
        $f = fopen($sqlfile, "rb");
        // 创建表缓冲变量
        $create_table = '';
        while (!feof($f)) {
            // 读取每一行sql
            $line = fgets($f);
            // 这一步为了将创建表合成完整的sql语句
            // 如果结尾没有包含';'(即为一个完整的sql语句，这里是插入语句)，并且不包含'ENGINE='(即创建表的最后一句)
            if (!preg_match('/;/', $line) || preg_match('/ENGINE=/', $line)) {
                // 将本次sql语句与创建表sql连接存起来
                $create_table .= $line;
                // 如果包含了创建表的最后一句
                if (preg_match('/ENGINE=/', $create_table)) {
                    //执行sql语句创建表
                    $this->_insert_into($create_table);
                    // 清空当前，准备下一个表的创建
                    $create_table = '';
                }
                // 跳过本次
                continue;
            }
            //执行sql语句
            $this->_insert_into($line);
        }
        fclose($f);
        return ['status'=>true, 'msg'=>'导入成功！'];
    }

    //插入单条sql语句
    private function _insert_into($sql)
    {
        if (!Db::query(trim($sql))) {
            return false;
        }
    }

    /*
     * -------------------------------数据库导入end---------------------------------
     */

    // 锁定数据库，以免备份或导入时出错
    private function lock($tablename, $op = "WRITE")
    {
        if (Db::query("lock tables " . $tablename . " " . $op))
            return true;
        else
            return false;
    }

}